package SquarePackage;

import be.kuleuven.cs.som.annotate.*;

/**
 * An enumeration of temperature scales. The class distinguishes 3 types of
 * temperature scales (Kelvin, Celsius, Fahrenheit).
 * 
 * @author Brecht Reynders
 * @version 1.0
 */
public enum TemperatureScale {
	KELVIN('K', 1, 0), CELSIUS('C', 1, 273.15), FAHRENHEIT('F', 9.00 / 5.00,
			459.67);

	/**
	 * Initializes a temperature scale with a given name and given values.
	 * 
	 * @param 	sign
	 *          The sign of this temperature scale.
	 * @note 	each scale can be written as a first order function of the Kelvin
	 *       	scale. Temperature in random scale = a * temperature value in
	 *       	Kelvin + b. a is the slope. b is the zeroValue.
	 * @param 	SLOPE
	 *          the slope in the first-order Kelvinfunction (see note).
	 * @param 	zeroValue
	 *          the zeroValue in the first-order Kelvinfunction (see note).
	 */
	TemperatureScale(char SIGN, double SLOPE, double ZERO_VALUE) {
		this.SIGN = SIGN;
		this.SLOPE = SLOPE;
		this.ZERO_VALUE = ZERO_VALUE;
	}

	/**
	 * Variable registering the sign of the temperature scale.
	 */
	private final char SIGN;

	/**
	 * Variable registering the slope of the temperature scale in function of
	 * the Kelvin scale.
	 * 
	 * @note each scale can be written as a first order function of the Kelvin
	 *       scale. Temperature in random scale = a * temperature value in
	 *       Kelvin + b a is the slope. b is the zeroValue.
	 */
	private final double SLOPE;

	/**
	 * Variable registering the zeroValue of the temperature scale in function
	 * of the Kelvin scale.
	 * 
	 * @note each scale can be written as a first order function of the
	 *       Kelvinscale. Temperature in random scale = a * temperature value in
	 *       Kelvin + b a is the slope. b is the zeroValue.
	 */
	private final double ZERO_VALUE;

	/**
	 * Return the sign of the temperature scale.
	 */
	@Basic @Immutable
	public final Character getSign() {
		return SIGN;
	}

	/**
	 * Return the slope of the temperature scale.
	 */
	@Basic @Model @Immutable
	private final double getslope() {
		return SLOPE;
	}

	/**
	 * Return the zero value of the temperature scale.
	 */
	@Basic @Model @Immutable
	public final double getZeroValue() {
		return ZERO_VALUE;
	}
	
	/**
	 * Return the absolute minimum of this temperature scale.
	 */
	@Immutable
	public final double getAbsoluteMinimum(){
		return -getZeroValue();
	}

	/**
	 * Return the temperature value in an asked scale.
	 * 
	 * @param 	temperature
	 *          Temperature in a given scale.
	 * @param 	newScale
	 *          The new scale in which the temperature must be converted.
	 * @return 	The temperature value in the given temperature scale.
	 * 		  | toOtherTemperature(toKelvin(temperature),newScale)
	 * @throws 	IllegalArgumentException
	 *          The given scale does not exist. 
	 *        | !isValidScale(newScale)
	 */
	public double changeTemperatureScale(double temperature,
			TemperatureScale newScale) throws IllegalArgumentException {
		if (!isValidScale(newScale))
			throw new IllegalArgumentException("not a valid scale");
		temperature = toKelvin(temperature);
		return toOtherTemperatureScale(temperature, newScale);
	}

	/**
	 * Return the temperature value in Kelvin
	 * 
	 * @param 	temperature
	 *          The temperature value to be changed.
	 * @return 	The temperature value in Kelvin.
	 * 		  | if(temperatureScale == KELVIN)
	 *        |   result == temperature 
	 *        | if(temperatureScale == CELSIUS) 
	 *        |   result == temperature + getZeroValue()
	 *        | if(temperatureScale == FAHRENHEIT) 
	 *        |   result == (temperature + getZeroValue())/getSlope()
	 * @throws 	IllegalArgumentException
	 *          The temperature is a temperature lower than the absolute
	 *          minimum (0Kelvin). 
	 *       | temperature < 0.0
	 */
	@Model
	private double toKelvin(double temperature) throws IllegalArgumentException {
		temperature = (temperature + this.getZeroValue()) / this.getslope();
		if (temperature < 0.0)
			throw new IllegalArgumentException(
					"temperature can't be lower then 0K");
		return temperature;
	}

	/**
	 * Return the temperature value in a given temperature scale.
	 * 
	 * @param 	temperature
	 *          The temperature value in Kelvin to change.
	 * @pre 	temperature in Kelvin is higher than 0K.
	 * @return 	The temperature value in the given temperature scale. 
	 * 		  | return = temperature * newScale.getSlope() 
	 * 							- newScale.getZeroValue()
	 */
	@Model
	private double toOtherTemperatureScale(double temperature,
			TemperatureScale newScale) {
		return temperature * newScale.getslope() - newScale.getZeroValue();
	}

	/**
	 * Return a boolean indicating whether of not the new scale is a valid
	 * scale.
	 * 
	 * @param 	newScale
	 *          The new scale for the temperature scale.
	 * @return 	True only if the scale is Celsius, Kelvin or Fahrenheit 
	 * 		  | result == (newScale == TemperatureScale.KELVIN 
	 * 		  | || newScale == TemperatureScale.CELSIUS 
	 * 		  | || newScale == TemperatureScale.FAHRENHEIT)
	 */
	public boolean isValidScale(TemperatureScale newScale) {
		for (TemperatureScale scale : TemperatureScale.values()) {
			if (scale.equals(newScale)) {
				return true;
			}
		}
		return false;
	}
}
